#include <esp_wifi.h>
#include <WiFi.h>
#include <WiFiClient.h>

#include <esp_camera.h>
#include <esp_timer.h>

#include <ESP_WiFiManager.h>   

#include "Base64.h"
#include <ArduinoJson.h>
#include <MQTT.h>

#define MQTT_HEART_TIMER_INTERVAL_MS        5000 
#define CAMERA_MODEL_AI_THINKER // Has PSRAM

int relayPin2 = 15; 

// OV2640 camera module pins (CAMERA_MODEL_AI_THINKER)
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

#define ESP_getChipId()   ((uint32_t)ESP.getEfuseMac())

const char* mqtt_server = "mq.tongxinmao.com";//MQTT服务器的地址 
const int mqtt_port=18830;//MQTT服务器端口号
String m_ChipId;
boolean takeNewPhoto = false;
boolean isTakeNewPhoto = false;
char encodedString[55000];
char sendencodestring[512];
camera_fb_t * pic = NULL;
long lastMsg = 0;  
size_t m_cursendcdepos = 0;
size_t m_cursendcdelength = 400;
size_t m_curtotalsendlength = 0;

WiFiClient net;
MQTTClient mqttclient(512);
esp_timer_handle_t fw_timer_handle = NULL;

void initesp32cam(void) 
{
  // OV2640 camera module
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  config.frame_size = FRAMESIZE_XGA;
  config.jpeg_quality = 12;
  config.fb_count = 1;
  
  // Camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    ESP.restart();
  }  
}

void capturePhotoSaveSpiffs( void ) {
  if(!mqttclient.connected()) {
    takeNewPhoto=false;
    Serial.println("start encode error2."); 
    return;
  }
  
  if(!isTakeNewPhoto) {
    pic = esp_camera_fb_get();   
    size_t pic_len = pic->len;    
    esp_camera_fb_return(pic); 

    if(pic_len > 42000) {
      takeNewPhoto=false;
      Serial.println("start encode error."); 
      Serial.println(pic_len);
      String tpmparam = "{\"device\":\"" +m_ChipId+ "\"}";
      mqttclient.publish("home/photo/error",tpmparam.c_str());
      return;
    }

    memset(encodedString,0,sizeof(encodedString));

    Serial.println("start encode."); 
    Serial.println(pic_len); 
        
    m_curtotalsendlength = Base64.encode(encodedString, (char *)pic->buf, pic_len); 
    Serial.println(m_curtotalsendlength);  
    //Serial.println(encodedString);    

    isTakeNewPhoto=true;
    m_cursendcdepos = 0;
    m_cursendcdelength = 400;

    String tpmparam = "{\"device\":\"" +m_ChipId+ "\"}";
    mqttclient.publish("home/photo/start",tpmparam.c_str());
  }

  if(m_cursendcdepos + m_cursendcdelength > m_curtotalsendlength) {
    m_cursendcdelength = m_curtotalsendlength - m_cursendcdepos;
  }

  memset(sendencodestring,0,sizeof(sendencodestring));
  memcpy(sendencodestring,encodedString+m_cursendcdepos,m_cursendcdelength);
  //Serial.println(sendencodestring);

  mqttclient.publish("home/photo/data",sendencodestring );

  m_cursendcdepos += m_cursendcdelength;

  if(m_cursendcdepos >= m_curtotalsendlength) {
    String tpmparam = "{\"device\":\"" +m_ChipId+ "\"}";
    mqttclient.publish("home/photo/end",tpmparam.c_str());
    Serial.println("end encode."); 
    takeNewPhoto = false;
  }
}

void mqtt_heart_TimerHandler(void *arg)
{
  if(!mqttclient.connected()) return;
  
 /* StaticJsonDocument<200> actiondoc;
  actiondoc["device"] = m_ChipId;
  actiondoc["status"] = "on";
  char actiondocDoc[measureJson(actiondoc) + 1];
  serializeJson(actiondoc, actiondocDoc, measureJson(actiondoc) + 1); */

  String tpmparam = "{\"device\":\"" +m_ChipId+ "\",\"status\":\"on\"}";
  mqttclient.publish("home/status/",tpmparam.c_str());
}

void mqtt_reconnect() {//等待，直到连接上服务器
  if(WiFi.status() != WL_CONNECTED)
  {
    mqttclient.disconnect();
    return;
  }
   
  while (!mqttclient.connected()) {//如果没有连接上
    if(WiFi.status() != WL_CONNECTED) {
      mqttclient.disconnect();
      Serial.println("MQTT disconneded.");
      break; 
    }

    Serial.println("MQTT server connecting...");    
    if (mqttclient.connect("biloba"+random(999999999))) {//接入时的用户名，尽量取一个很不常用的用户名
      Serial.println("mqtt server connected.");

      // 开始订阅消息
      mqttclient.subscribe("AKINGLED",2);
      mqttclient.subscribe("takepicture");

      // 消息订阅成功
      Serial.println("message subscribe success.");
    } else {
      Serial.println("connect mqtt server failed,try again connect mqtt server in 5 seconds");//延时5秒后重新连接           
      delay(5000);
    }
  }
}

void mqtt_messageReceived(String &topic, String &payload) {
  Serial.println("incoming: " + topic + " - " + payload);

  StaticJsonDocument<200> doc; //声明一个JsonDocument对象
  DeserializationError error = deserializeJson(doc, payload.c_str()); //反序列化JSON数据

  if(!error) {
    String action = doc["action"];
        
    if(topic == "takepicture") {
      if (action.equals("on") == true && !takeNewPhoto) {
        Serial.println("user takepicture.");

        takeNewPhoto=true;
        isTakeNewPhoto=false;
      }
    }
  }
}

void setup() {
  Serial.begin(115200);

  pinMode(relayPin2,INPUT); 

  m_ChipId = (String)ESP_getChipId();

  ESP_WiFiManager ESP_wifiManager("AutoConnectAP");
  ESP_wifiManager.setDebugOutput(true);
  ESP_wifiManager.setTimeout(180);
  ESP_wifiManager.setAPStaticIPConfig(IPAddress(192, 168, 4, 1), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0));
  ESP_wifiManager.setMinimumSignalQuality(-1);
  
  String Router_SSID = ESP_wifiManager.WiFi_SSID();
  String Router_Pass = ESP_wifiManager.WiFi_Pass();
  Serial.println("Stored: SSID = " + Router_SSID + ", Pass = " + Router_Pass);
  
  String AP_PARAMES = "ESP_" + m_ChipId;    
  ESP_wifiManager.autoConnect(AP_PARAMES.c_str(),AP_PARAMES.c_str(),Router_SSID.c_str(),Router_Pass.c_str());

  Serial.println("wifi connected start mqtt config.");

  mqttclient.begin(mqtt_server,mqtt_port, net);
  mqttclient.onMessage(mqtt_messageReceived);  

  Serial.println("mqtt config end start cam config.");  

  initesp32cam();

  Serial.println("cam config end start mqtthearttimer.");   

  //定时器结构体初始化
  /*esp_timer_create_args_t periodic_timer_args;
  
  periodic_timer_args.callback = &mqtt_heart_TimerHandler;
  periodic_timer_args.arg = NULL;
  periodic_timer_args.name = "periodic";
 
  //定时器创建、启动  
  ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &fw_timer_handle));  
  ESP_ERROR_CHECK(esp_timer_start_periodic(fw_timer_handle, 10000 * 1000));//10秒回调*/

  Serial.println("connected...yeey :)");
}

void loop() {
  mqtt_reconnect();//确保连上服务器，否则一直等待。
  
  mqttclient.loop();//MUC接收数据的主循环函数。
  delay(10);

  if(takeNewPhoto) {
    capturePhotoSaveSpiffs();
  }
  
  long now = millis();
  if (now - lastMsg > 10000) {
    lastMsg = now;
    
    mqtt_heart_TimerHandler(0);
  }

  int pirValue = digitalRead(relayPin2);
  //Serial.println(pirValue);  

  if(pirValue == 1 && !takeNewPhoto) {
    Serial.println("auto takepicture.");

    takeNewPhoto=true;
    isTakeNewPhoto=false;
  }
}
